using System;
using System.IO;

namespace org.jawk.util
{
    /// <summary>Awk Parameters.</summary>
    /// <remarks>
    /// Awk Parameters.  It manages the command-line parameters accepted by Jawk.
    /// The parameters and their meanings are provided below:
    /// <ul>
    /// <li>-v name=val [-v name=val] ... <br />
    /// Variable assignments prior to execution of the script.
    /// <li>-F regexp <br />
    /// Field separator (FS).
    /// <li>-f filename <br />
    /// Use the text contained in filename as the script rather than
    /// obtaining it from the command-line.
    /// <li><i>Extension</i> -c <br />
    /// Write intermediate file.  Intermediate file can be used as
    /// an argument to -f.
    /// <li><i>Extension</i> -o filename <br />
    /// Output filename for intermediate file, tuples, or syntax tree.
    /// <li><i>Extension</i> -z <br />
    /// Compile to JVM rather than interpret it.
    /// <li><i>Extension</i> -Z <br />
    /// Compile to JVM rather and execute it.
    /// <li><i>Extension</i> -d <br />
    /// Compile results to destination directory instead of current working dir.
    /// <li><i>Extension</i> -s <br />
    /// Dump the intermediate code.
    /// <li><i>Extension</i> -S <br />
    /// Dump the syntax tree.
    /// <li><i>Extension</i> -x <br />
    /// Enables _sleep, _dump, and exec keywords/functions.
    /// <li><i>Extension</i> -y <br />
    /// Enables _INTEGER, _DOUBLE, and _STRING type casting keywords.
    /// <li><i>Extension</i> -t <br />
    /// Maintain array keys in sorted order (using a TreeMap instead of a HashMap)
    /// <li><i>Extension</i> -r <br />
    /// Do NOT trap for IllegalFormatException when using java.util.Formatter
    /// for sprintf and printf.
    /// <li><i>Extension</i> -ext <br />
    /// Enabled user-defined extensions.  Works together with the
    /// -Djava.extensions property.
    /// It also disables blank rule as mapping to a print $0 statement.
    /// <li><i>Extension</i> -ni <br />
    /// Do NOT consume stdin or files from ARGC/V through input rules.
    /// The motivation is to leave input rules for blocking extensions
    /// (i.e., Sockets, Dialogs, etc).
    /// </ul>
    /// followed by the script (if -f is not provided), then followed
    /// by a list containing zero or more of the following parameters:
    /// <ul>
    /// <li>name=val <br />
    /// Variable assignments occurring just prior to receiving input
    /// (but after the BEGIN blocks, if any).
    /// <li>filename <br />
    /// Filenames to treat as input to the script.
    /// </ul>
    /// <p>
    /// If no filenames are provided, stdin is used as input
    /// to the script (but only if there are input rules).
    /// </remarks>
    public class AwkParameters
    {
        private System.Type mainclass;

        /// <summary>Dump usage to stderr; exit with a non-zero error code.</summary>
        /// <remarks>Dump usage to stderr; exit with a non-zero error code.</remarks>
        private void Usage(System.IO.TextWriter dest, string extension_description)
        {
            //String cls = Awk.class.getName();
            string cls = mainclass.FullName;
            dest.WriteLine("usage:");
            dest.WriteLine("java ... " + cls + " [-F fs_val]" + (extension_description == null
                 ? string.Empty + " [-f script-filename]" + " [-o output-filename]" + " [-c]" +
                " [-z]" + " [-Z]" + " [-d dest-directory]" + " [-S]" + " [-s]" + " [-x]" + " [-y]"
                 + " [-r]" + " [-ext]" + " [-ni]" : string.Empty) + " [-t]" + " [-v name=val]..."
                 + (extension_description == null ? " [script]" : string.Empty) + " [name=val | input_filename]..."
                );
            dest.WriteLine();
            dest.WriteLine(" -F fs_val = Use fs_val for FS.");
            if (extension_description == null)
            {
                dest.WriteLine(" -f filename = Use contents of filename for script.");
            }
            dest.WriteLine(" -v name=val = Initial awk variable assignments.");
            dest.WriteLine();
            dest.WriteLine(" -t = (extension) Maintain array keys in sorted order.");
            if (extension_description == null)
            {
                dest.WriteLine(" -c = (extension) Compile to intermediate file. (default: a.ai)");
                dest.WriteLine(" -o = (extension) Specify output file.");
                dest.WriteLine(" -z = (extension) | Compile for JVM. (default: AwkScript.class)");
                dest.WriteLine(" -Z = (extension) | Compile for JVM and execute it. (default: AwkScript.class)"
                    );
                dest.WriteLine(" -d = (extension) | Compile to destination directory.  (default: pwd)"
                    );
                dest.WriteLine(" -S = (extension) Write the syntax tree to file. (default: syntax_tree.lst)"
                    );
                dest.WriteLine(" -s = (extension) Write the intermediate code to file. (default: avm.lst)"
                    );
                dest.WriteLine(" -x = (extension) Enable _sleep, _dump as keywords, and exec as a builtin func."
                    );
                //dest.println("                  (Note: exec not enabled in compiled mode.)");
                dest.WriteLine("                  (Note: exec enabled only in interpreted mode.)"
                    );
                dest.WriteLine(" -y = (extension) Enable _INTEGER, _DOUBLE, and _STRING casting keywords."
                    );
                dest.WriteLine(" -r = (extension) Do NOT hide IllegalFormatExceptions for [s]printf."
                    );
                dest.WriteLine("-ext= (extension) Enable user-defined extensions. (default: not enabled)"
                    );
                dest.WriteLine("-ni = (extension) Do NOT process stdin or ARGC/V through input rules."
                    );
                dest.WriteLine("                  (Useful for blocking extensions.)");
                //dest.println("                  (Note: -ext & -ni not available in compiled mode.)");
                dest.WriteLine("                  (Note: -ext & -ni available only in interpreted mode.)"
                    );
            }
            else
            {
                // separate the extension description
                // from the -t argument description (above)
                // with a newline
                dest.WriteLine();
                dest.WriteLine(extension_description);
            }
            dest.WriteLine();
            dest.WriteLine(" -h or -? = (extension) This help screen.");
            if (dest == System.Console.Out)
            {
                System.Environment.Exit(0);
            }
            else
            {
                // invalid usage ... return a non-zero error code
                System.Environment.Exit(1);
            }
        }

        /// <summary>Provide text representation of the parameters to stdout.</summary>
        /// <remarks>Provide text representation of the parameters to stdout.</remarks>
        internal virtual void Dump()
        {
            System.Console.Out.WriteLine("Awk Parameters");
            System.Console.Out.WriteLine("--------------");
            System.Console.Out.WriteLine("initial_variables = " + initial_variables);
            System.Console.Out.WriteLine("name_value_filename_list = " + name_value_filename_list
                );
            System.Console.Out.WriteLine("script_filename = " + script_filename);
            System.Console.Out.WriteLine("script_reader = " + script_reader);
            System.Console.Out.WriteLine("should_compile = " + should_compile);
            System.Console.Out.WriteLine("should_compile_and_run = " + should_compile_and_run
                );
            System.Console.Out.WriteLine("initial_fs_value = " + initial_fs_value);
            System.Console.Out.WriteLine("dump_syntax_tree = " + dump_syntax_tree);
            System.Console.Out.WriteLine("dump_intermediate_code = " + dump_intermediate_code
                );
            System.Console.Out.WriteLine("additional_functions = " + additional_functions);
            System.Console.Out.WriteLine("additional_type_functions = " + additional_type_functions
                );
            System.Console.Out.WriteLine("sorted_array_keys = " + sorted_array_keys);
            System.Console.Out.WriteLine("trap_illegal_format_exceptions = " + trap_illegal_format_exceptions
                );
            System.Console.Out.WriteLine("write_to_intermediate_file = " + write_to_intermediate_file
                );
            System.Console.Out.WriteLine("output_filename = " + output_filename);
            System.Console.Out.WriteLine("dest_directory = " + dest_directory);
            System.Console.Out.WriteLine();
        }

        /// <summary>
        /// Contains variable assignments which are applied prior to
        /// executing the script (-v assignments).
        /// </summary>
        /// <remarks>
        /// Contains variable assignments which are applied prior to
        /// executing the script (-v assignments).
        /// </remarks>
        public System.Collections.Generic.IDictionary<string, object> initial_variables =
            new System.Collections.Generic.Dictionary<string, object>();

        /// <summary>Contains name=value or filename entries or Stream (instead of filename).</summary>
        /// <remarks>
        /// Contains name=value or filename entries.
        /// Order is important, which is why name=value and filenames
        /// are listed in the same List container.
        /// 
        /// list of object because you can find string or Stream type
        /// </remarks>
        public System.Collections.Generic.IList<object> name_value_filename_list = new System.Collections.Generic.List<object>();

        /// <summary>Script filename (if provided).</summary>
        /// <remarks>
        /// Script filename (if provided).
        /// If null, using the first non-"-" parameter.
        /// </remarks>
        public string script_filename = null;

        /// <summary>Reader providing the script.</summary>
        /// <remarks>
        /// Reader providing the script.
        /// If script comes from the command-line, a StringReader is used.
        /// </remarks>
        public TextReader script_reader;

        /// <summary>Whether to interpret or compile the script.</summary>
        /// <remarks>
        /// Whether to interpret or compile the script.
        /// Initial value is set to false (interpret).
        /// </remarks>
        public bool should_compile = false;

        /// <summary>Whether to compile and execute the script.</summary>
        /// <remarks>
        /// Whether to compile and execute the script.
        /// Initial value is set to false (interpret).
        /// </remarks>
        public bool should_compile_and_run = false;

        /// <summary>Initial FS value.</summary>
        /// <remarks>Initial FS value.  Initially set to null (default FS value).</remarks>
        public string initial_fs_value = null;

        /// <summary>Whether to dump the syntax tree; false by default.</summary>
        /// <remarks>Whether to dump the syntax tree; false by default.</remarks>
        public bool dump_syntax_tree = false;

        /// <summary>Whether to dump the intermediate code; false by default.</summary>
        /// <remarks>Whether to dump the intermediate code; false by default.</remarks>
        public bool dump_intermediate_code = false;

        /// <summary>
        /// Whether to enable additional functions (_sleep/_dump);
        /// false by default.
        /// </summary>
        /// <remarks>
        /// Whether to enable additional functions (_sleep/_dump);
        /// false by default.
        /// </remarks>
        public bool additional_functions = false;

        /// <summary>
        /// Whether to enable additional functions (_INTEGER/_DOUBLE/_STRING);
        /// false by default.
        /// </summary>
        /// <remarks>
        /// Whether to enable additional functions (_INTEGER/_DOUBLE/_STRING);
        /// false by default.
        /// </remarks>
        public bool additional_type_functions = false;

        /// <summary>Whether to maintain array keys in sorted order; false by default.</summary>
        /// <remarks>Whether to maintain array keys in sorted order; false by default.</remarks>
        public bool sorted_array_keys = false;

        /// <summary>Whether to trap illegalformatexceptions for [s]printf; true by default.</summary>
        /// <remarks>Whether to trap illegalformatexceptions for [s]printf; true by default.</remarks>
        public bool trap_illegal_format_exceptions = true;

        /// <summary>Whether user extensions are enabled; false by default.</summary>
        /// <remarks>Whether user extensions are enabled; false by default.</remarks>
        public bool user_extensions = false;

        /// <summary>Whether Jawk consumes stdin or ARGV file input; false by default.</summary>
        /// <remarks>Whether Jawk consumes stdin or ARGV file input; false by default.</remarks>
        public bool no_input = false;

        /// <summary>Write to intermediate file.</summary>
        /// <remarks>Write to intermediate file.  Initially set to false.</remarks>
        public bool write_to_intermediate_file = false;

        /// <summary>Output filename.</summary>
        /// <remarks>
        /// Output filename.
        /// Initially set to null (use appropriate default filename).
        /// </remarks>
        public string output_filename = null;

        /// <summary>Compiled destination directory (if provided).</summary>
        /// <remarks>
        /// Compiled destination directory (if provided).
        /// If null, using null (i.e., present working directory).
        /// </remarks>
        private string dest_directory = null;


        public AwkParameters()
        {
            mainclass = typeof(Awk);
        }

        /// <summary>
        /// Allocate the parameters, using the command line parameters
        /// from the VM entry point (main).
        /// </summary>
        /// <remarks>
        /// Allocate the parameters, using the command line parameters
        /// from the VM entry point (main).
        /// <p>
        /// The command-line argument semantics are as follows:</p>
        /// <ul>
        /// <li>First, "-" arguments are processed until first non-"-" argument
        /// is encountered, or the "-" itself is provided.</li>
        /// <li>Next, a script is expected (unless the -f argument was provided).</li>
        /// <li>Then, subsequent parameters are passed into the script</li>
        /// via the ARGC/ARGV variables.
        /// </ul>
        /// </remarks>
        /// <param name="mainclass">The main class to print when displaying usage.</param>
        /// <param name="args">The command-line arguments provided by the user.</param>
        /// <param name="extension_description">
        /// a text description of extensions that
        /// are enabled (for compiled scripts)
        /// </param>
        public AwkParameters(System.Type mainclass, string[] args, string extension_description
            )
        {
            this.mainclass = mainclass;
            int arg_idx = 0;
            try
            {
                // optional parameter mode (i.e. args[i].charAt(0) == '-')
                while (arg_idx < args.Length)
                {
                    //assert args[arg_idx] != null;
                    if (args[arg_idx].Length == 0)
                    {
                        throw new System.ArgumentException("zero-length argument at position " + (arg_idx
                             + 1));
                    }
                    if (args[arg_idx][0] != '-')
                    {
                        // no more -X arguments
                        break;
                    }
                    else

                        if (args[arg_idx].Equals("-"))
                        {
                            // no more -X arguments
                            ++arg_idx;
                            break;
                        }
                        else

                            if (args[arg_idx].Equals("-v"))
                            {
                                CheckParameterHasArgument(args, arg_idx);
                                ++arg_idx;
                                CheckInitialVariableFormat(args[arg_idx]);
                                AddInitialVariable(args[arg_idx]);
                            }
                            else if (args[arg_idx].Equals("-f"))
                            {
                                CheckParameterHasArgument(args, arg_idx);
                                ++arg_idx;
                                script_filename = args[arg_idx];
                            }
                            else if (args[arg_idx].Equals("-d"))
                            {
                                CheckParameterHasArgument(args, arg_idx);
                                ++arg_idx;
                                dest_directory = args[arg_idx];
                            }
                            else if (args[arg_idx].Equals("-c"))
                            {
                                write_to_intermediate_file = true;
                            }
                            else if (args[arg_idx].Equals("-o"))
                            {
                                CheckParameterHasArgument(args, arg_idx);
                                ++arg_idx;
                                output_filename = args[arg_idx];
                            }
                            else if (args[arg_idx].Equals("-z"))
                            {
                                should_compile = true;
                            }
                            else if (args[arg_idx].Equals("-Z"))
                            {
                                should_compile_and_run = true;
                            }
                            else if (args[arg_idx].Equals("-S"))
                            {
                                dump_syntax_tree = true;
                            }
                            else if (args[arg_idx].Equals("-s"))
                            {
                                dump_intermediate_code = true;
                            }
                            else if (args[arg_idx].Equals("-x"))
                            {
                                additional_functions = true;
                            }
                            else if (args[arg_idx].Equals("-y"))
                            {
                                additional_type_functions = true;
                            }
                            else if (args[arg_idx].Equals("-t"))
                            {
                                sorted_array_keys = true;
                            }
                            else if (args[arg_idx].Equals("-r"))
                            {
                                trap_illegal_format_exceptions = false;
                            }
                            else if (args[arg_idx].Equals("-F"))
                            {
                                CheckParameterHasArgument(args, arg_idx);
                                ++arg_idx;
                                initial_fs_value = args[arg_idx];
                            }
                            else if (args[arg_idx].Equals("-ext"))
                            {
                                user_extensions = true;
                            }
                            else if (args[arg_idx].Equals("-ni"))
                            {
                                no_input = true;
                            }
                            else if (args[arg_idx].Equals("-h") || args[arg_idx].Equals("-?"))
                            {
                                Usage(System.Console.Out, extension_description);
                            }
                            else
                            {
                                throw new System.ArgumentException("unknown parameter: " + args[arg_idx]);
                            }

                    ++arg_idx;
                }
                if (extension_description == null)
                {
                    // script mode (if -f not provided)
                    if (script_filename == null)
                    {
                        if (arg_idx >= args.Length)
                        {
                            throw new System.ArgumentException("Awk script not provided.");
                        }
                        string script_text = args[arg_idx++];
                        script_reader = new StringReader(script_text);
                    }
                    else
                    {
                        try
                        {
                            is_intermediate_file = script_filename.EndsWith(".ai");
                            if (is_intermediate_file)
                            {
                                if_input_stream = new FileStream(script_filename, FileMode.Open);
                            }
                            else
                            {
                                // read from file
                                script_reader = new StreamReader(script_filename);
                            }
                        }
                        catch (FileNotFoundException fnfe)
                        {
                            Console.Error.WriteLine(fnfe.ToString());
                            Usage(System.Console.Error, extension_description);
                        }
                    }
                }
            }
            catch (System.ArgumentException iae)
            {
                Console.Error.WriteLine(iae.ToString());
                Usage(System.Console.Error, extension_description);
                throw;
            }
            // name=val or filename mode
            while (arg_idx < args.Length)
            {
                string name_value_filename = args[arg_idx++];
                name_value_filename_list.Add(name_value_filename);
            }
        }

        /// <summary>Validates that a required argument is provided with the parameter.</summary>
        /// <remarks>
        /// Validates that a required argument is provided with the parameter.
        /// This could have been done with a simple if (arg_idx+1 &gt;= args.length) ...
        /// However,
        /// <ul>
        /// <li>this normalizes the implementation throughout the class.
        /// <li>additional assertions are performed.
        /// </ul>
        /// </remarks>
        /// <exception cref="System.ArgumentException"></exception>
        private static void CheckParameterHasArgument(string[] args, int arg_idx)
        {
            //assert arg_idx < args.length;
            //assert args[arg_idx].charAt(0) == '-';
            if (arg_idx + 1 >= args.Length)
            {
                throw new System.ArgumentException("Need additional argument for " + args[arg_idx]);
            }
        }

        /// <summary>Makes sure the argument is of the form name=value.</summary>
        /// <remarks>Makes sure the argument is of the form name=value.</remarks>
        /// <exception cref="System.ArgumentException"></exception>
        private static void CheckInitialVariableFormat(string v_arg)
        {
            int equals_count = 0;
            int length = v_arg.Length;
            for (int i = 0; equals_count <= 1 && i < length; i++)
            {
                if (v_arg[i] == '=')
                {
                    ++equals_count;
                }
            }
            if (equals_count != 1)
            {
                throw new System.ArgumentException("v_arg \"" + v_arg + "\" must be of the form name=value"
                    );
            }
        }

        private void AddInitialVariable(string v_arg)
        {
            int eq_idx = v_arg.IndexOf('=');
            //assert eq_idx >= 0;
            string name = v_arg.Substring(0, eq_idx);
            string value_string = v_arg.Substring(eq_idx + 1);
            object value;
            // deduce type
            try
            {
                value = int.Parse(value_string);
            }
            catch (FormatException)
            {
                try
                {
                    value = double.Parse(value_string, System.Globalization.CultureInfo.InvariantCulture.NumberFormat);
                }
                catch (FormatException)
                {
                    value = value_string;
                }
            }
            // note: can overwrite previously defined variables
            initial_variables[name] = value;
        }

        private bool is_intermediate_file;

        private Stream if_input_stream;

        /// <summary>Determine if the -f optarg is an intermediate file.</summary>
        /// <remarks>
        /// Determine if the -f optarg is an intermediate file.
        /// Intermediate files end with the .ai extension.  No other
        /// determination is made whether the file is an intermediate
        /// file or not.  That is, the content of the file is not checked.
        /// </remarks>
        /// <returns>
        /// true if the -f optarg is an intermediate file (a file
        /// ending in .ai), false otherwise.
        /// </returns>
        public virtual bool IsIntermediateFile()
        {
            return is_intermediate_file;
        }

        /// <summary>Obtain the Reader containing the script.</summary>
        /// <remarks>
        /// Obtain the Reader containing the script.  This returns non-null
        /// only if the -f argument is utilized.
        /// </remarks>
        /// <returns>
        /// The reader which contains the script, null if no script
        /// file is provided.
        /// </returns>
        public virtual TextReader ScriptReader()
        {
            return script_reader;
        }

        /// <summary>Obtain the InputStream containing the intermediate file.</summary>
        /// <remarks>
        /// Obtain the InputStream containing the intermediate file.
        /// This returns non-null only if the -f argument is utilized and it
        /// refers to an intermediate file.
        /// </remarks>
        /// <returns>
        /// The reader which contains the intermediate file, null if
        /// either the -f argument is not used, or the argument does not
        /// refer to an intermediate file.
        /// </returns>
        public virtual Stream IfInputStream()
        {
            return if_input_stream;
        }

        /// <param name="default_filename">
        /// The filename to return if -o argument
        /// is not used.
        /// </param>
        /// <returns>
        /// the optarg for the -o parameter, or the default_filename
        /// parameter if -o is not utilized.
        /// </returns>
        public virtual string OutputFilename(string default_filename)
        {
            if (output_filename == null)
            {
                return default_filename;
            }
            else
            {
                return output_filename;
            }
        }

        /// <returns>
        /// the optarg for the -d parameter, or null
        /// if -d is not utilized.
        /// </returns>
        public virtual string DestDirectory()
        {
            return dest_directory;
        }

        /// <summary>Provides a description of extensions that are enabled/disabled.</summary>
        /// <remarks>
        /// Provides a description of extensions that are enabled/disabled.
        /// The default compiler implementation uses this method
        /// to describe extensions which are compiled into the script.
        /// The description is then provided to the user within the usage.
        /// </remarks>
        /// <returns>A description of the extensions which are enabled/disabled.</returns>
        public virtual string ExtensionDescription()
        {
            // additional_functions (_sleep, _dump)
            // additional_type_functions (_INTEGER, _DOUBLE, _STRING)
            // sorted_array_keys
            // trap_illegal_format_exceptions (for FALSE)
            string retval = string.Empty + (additional_functions ? ", _sleep & _dump enabled"
                 : string.Empty) + (additional_type_functions ? ", _INTEGER, _DOUBLE, _STRING enabled"
                 : string.Empty) + (sorted_array_keys ? ", associative array keys are sorted" :
                string.Empty) + (!trap_illegal_format_exceptions ? ", IllegalFormatExceptions NOT trapped"
                 : string.Empty);
            if (retval.Length > 0)
            {
                return "{extensions: " + retval.Substring(2) + "}";
            }
            else
            {
                return "{no compiled extensions utilized}";
            }
        }
    }
}
